package com.alex.maze.graphics;

import com.alex.maze.Art;
import com.alex.maze.Game;
import com.alex.maze.entities.Entity;
import com.alex.maze.level.block.Block;
import com.alex.maze.level.Level;

/*  Kasnije da objasnim sve ovo posto ima ovde jos dosta posla... blah... :( */
//  Najverovatnije imam round-off greske... ajde da vidimo sta i kako.
//  Resio sam round-off gresku sad bi trebalo da je sve ok!
public class Bitmap3D extends Bitmap
{
    private double[] zBuffer;
    private double[] zBufferZidovi;
    private double xKamera;
    private double yKamera;
    private double zKamera;
    private double rotCosinus;
    private double rotSinus;
    private double fov;
    private double xCentar;
    private double yCentar;
   
    public Bitmap3D(int sirina, int visina)
    {
        super(sirina, visina);
       
        zBuffer = new double[sirina * visina]; // Hmmm..., proveri kasnije!
        zBufferZidovi = new double[sirina];
        
    }
   
    public void render(Game igra)
    {
        for (int x = 0; x < BSIRINA; x++)
        {
            zBufferZidovi[x] = 0;
        }
        for (int i = 0; i < BSIRINA * BVISINA; i++)
        {
            zBuffer[i] = 10000; // Samo renderuje pod...
        }
        
        //double oci = Math.sin(igra.vreme / 10.0) * 0.5; // Head bobing da li ce ovo da bude korisno?
        xKamera = igra.igrac.x - Math.sin(igra.igrac.rot) * 0.3;
        yKamera = igra.igrac.z - Math.cos(igra.igrac.rot) * 0.3;
        zKamera = -0.2;
        
        xCentar = BSIRINA / 2.0;
        yCentar = BVISINA / 3.0; // default je 2.0 ali i ovako ne izgleda lose
       
        double rotacija = igra.igrac.rot; //igra.vreme / 40.0;
       
        rotCosinus = Math.cos(rotacija);
        rotSinus = Math.sin(rotacija);
       
        /*  fov znaci field of view sto na srpskom znaci polje pogleda??? */
        fov = BVISINA;
        
        
        Level level = igra.nivo;
        int r = 6; // default 5, draw distance za zidove
        
        // Hmmmm, da ja mozda ne mesam x i z axis?!?
        // Kod ne izgleda lepo
        // O tome cu kasnije da brinem
        // Bitno je da radi :)6
        int xCenter = (int) Math.floor(xKamera);
        int zCenter = (int) Math.floor(yKamera); //Okej sad je sve ok!
        
        for (int zb = zCenter - r; zb <= zCenter + r; zb++) // yblock
        {
            for (int xb = xCenter - r; xb <= xCenter + r; xb++) // xblock
            {
                Block c = level.getBlock(xb, zb);
                Block e = level.getBlock(xb + 1, zb); // Istok
                Block s = level.getBlock(xb, zb + 1); // Jug
                
                
                if (c.solidRender)
                {
                    if (!e.solidRender)
                    {
                        renderZid(xb + 1, zb + 1, xb + 1, zb, c.tex, c.boja);
                    }
                    if (!s.solidRender)
                    {
                        renderZid(xb, zb + 1, xb + 1, zb + 1, c.tex, (c.boja & 0xFEFEFE) >> 1);
                    }
                }
                else
                {
                    if (e.solidRender)
                    {
                        renderZid(xb + 1, zb, xb + 1, zb + 1, e.tex, e.boja);
                    }
                    if (s.solidRender)
                    {
                        renderZid(xb + 1, zb + 1, xb, zb + 1, s.tex, (s.boja & 0xFEFEFE) >> 1);
                    }
                }
            }
        }
        
        for (int zb = zCenter - r; zb <= zCenter + r; zb++) // yblock
        {
            for (int xb = xCenter - r; xb <= xCenter + r; xb++) // xblock
            {
                Block c = level.getBlock(xb, zb);
                
                for (int j = 0; j < c.entities.size(); j++)
                {
                    Entity e = c.entities.get(j);
                    for (int i = 0; i < e.sprites.size(); i++)
                    {
                        Sprite sprite = e.sprites.get(i);
                        renderSprite(e.x + sprite.x, 0 - sprite.y, e.z + sprite.z, sprite.tex, sprite.boja);
                    }
                }

                for (int i = 0; i < c.sprites.size(); i++)
                {
                    Sprite sprite = c.sprites.get(i);
                    renderSprite(xb + sprite.x, sprite.y, zb + sprite.z, sprite.tex, sprite.boja);
                }
            }
        }
        renderPod(level);
    }
    
    private void renderPod(Level nivo) // I plafon!
    {
        //if (this != null)
        //{
        //    return;
        //}
        //int bojaPoda = 0x302020;
        //int bojaPlafona = 0x303030;
       
        for (int y = 0; y < BVISINA; y++)
        {
            double yd = ((y + 0.5) - yCentar) / fov;
            boolean pod = true;
            
            //int boja = bojaPoda;
            // Pazi ovde!
            double zd = (4 - zKamera * 8/* + oci*/) / yd; // Okej oci vise nisu oci nego bi one mogle da simuliraju vodu u nekim slucajevima a head bobbing moram sada drugacije da implementiram.
           
            if (yd < 0)
            {
                 pod = false;
                //boja = bojaPlafona;
                // I ovde!
                zd = (4 + zKamera * 8/*- oci*/) / -yd; // Plafon kontrola. (default je 4 ili bilo koj broj koji je gore!
            }
           
           
            for (int x = 0; x < BSIRINA; x++)
            {
                
                if (zBuffer[x + y * BSIRINA] < zd)
                {
                    continue;
                }
                double xd = (xCentar - x) / fov; // Da li ce ovo kasnije da me ujede za guzicu?!?
               
                xd *= zd;
                        
                double xx = xd * rotCosinus + zd * rotSinus + (xKamera + 0.5) * 8; // mozda na kraju - 0.5?
                /*  Tekstura na podu i plafonu je bila okrenuta naopako, sada je sve u redu sredio sam samo sa - i + :) */
                double yy = zd * rotCosinus - xd * rotSinus + (yKamera + 0.5) * 8; // mozda na kraju - 0.5? 
               
                int xPik = (int) (xx * 2);
                int yPik = (int) (yy * 2);
                
                //if (xx < 0)
                //{
                //    xPik--;
                //}
                //if (yy < 0)
                //{
                //    yPik--;
                //}
                
                int xTile = xPik >> 4;
                int yTile = yPik >> 4;
                
                               
                Block block = nivo.getBlock(xTile, yTile);
                
                
                //int boja = pod ? block.bojaPoda : block.bojaPlafona; // Ne radi bas kako treba... bas uopste, no nema veze... Okej sredio sam...
                //int tekstura = pod ? block.teksturaPoda : block.teksturaPlafona;
                
                int boja = block.bojaPoda;
                int tekstura = block.teksturaPoda;
                
                //int boja = 0x303030;
                //int tekstura = x & 1;
                
                //boja = yd > 0 ? block.bojaPoda : 0xFF0000;
                
                if (!pod)
                {
                    boja = block.bojaPlafona;
                    tekstura = block.teksturaPlafona;
                }
                
                
                //boja = 0x111111;
                //boja += (xTile & 2) * 0x110000;
                //boja += (yTile & 2) * 0x001100;
               
                zBuffer[x + y * BSIRINA] = zd;
                pikseli[x + y * BSIRINA] = Art.podovi.pikseli[((xPik & 15) + (tekstura & 8) * 16) + ((yPik & 15) + (tekstura / 8) * 16) * 128] * boja; // Sa teksturom.
            }
        }
    }
    
    
    private void renderZid(double x0, double y0, double x1, double y1, int tex, int color) // Da li da radim boje u paintu ili ovde u javi!?
    {
        //if (this != null)
        //{
        //    return;
        //}
        double xc0 = ((x0 - 0.5) - xKamera) * 2;
        double yc0 = ((y0 - 0.5) - yKamera) * 2;
                 
        double xx0 = xc0 * rotCosinus - yc0 * rotSinus;
        double u0 = ((-0.5) - zKamera) * 2;
        double l0 = ((+0.5) - zKamera) * 2;
        double zz0 = yc0 * rotCosinus + xc0 * rotSinus;
        
                     double xc1 = ((x1 - 0.5) - xKamera) * 2;
        double yc1 = ((y1 - 0.5) - yKamera) * 2;
            
        double xx1 = xc1 * rotCosinus - yc1 * rotSinus;
        double u1 = ((-0.5) - zKamera) * 2;
        double l1 = ((+0.5) - zKamera) * 2;
        double zz1 = yc1 * rotCosinus + xc1 * rotSinus;
        
        double xt0 = 0;
        double xt1 = 16;
        
        double zClip = 0.2;
        
        if (zz0 < zClip && zz1 < zClip)
        {
            return;
        }
        if (zz0 < zClip)
        {
            double p = (zClip - zz0) / (zz1 - zz0);
            zz0 = zz0 + (zz1 - zz0) * p;
            xx0 = xx0 + (xx1 - xx0) * p;
            xt0 = xt0 + (xt1 - xt0) * p;
        }
        if (zz1 < zClip)
        {
            double p = (zClip - zz0) / (zz1 - zz0);
            zz1 = zz0 + (zz1 - zz0) * p;
            xx1 = xx0 + (xx1 - xx0) * p;
            xt1 = xt0 + (xt1 - xt0) * p;
        }
        
        double xPiksel0 = xCentar - (xx0 / zz0 * fov);
        double xPiksel1 = xCentar - (xx1 / zz1 * fov);
        
        if (xPiksel0 >= xPiksel1)
        {
            return;
        }
        int xp0 = (int) Math.ceil(xPiksel0);
        int xp1 = (int) Math.ceil(xPiksel1);
        if (xp0 < 0)
        {
            xp0 = 0;
        }
        if (xp1 > BSIRINA)
        {
            xp1 = BSIRINA;
        }
        
        double yPiksel00 = (u0 / zz0 * fov + yCentar);
        double yPiksel01 = (l0 / zz0 * fov + yCentar);
        
        double yPiksel10 = (u1 / zz1 * fov + yCentar);
        double yPiksel11 = (l1 / zz1 * fov + yCentar);
        
        double iz0 = 1 / zz0;
        double iz1 = 1 / zz1;
        
        double iza = iz1 - iz0;
        
        double ixt0 = xt0 * iz0;
        double ixta = xt1 * iz1 - ixt0;
        double iw = 1 / (xPiksel1 - xPiksel0);
        
        for(int x = xp0; x < xp1; x++)
        {
            double pr = (x - xPiksel0) * iw;
            
            double iz = iz0 + iza * pr;
            
            if (zBufferZidovi[x] > iz)
            {
                continue;
            }
            zBufferZidovi[x] = iz;
            
            int xTex = (int) ((ixt0 + ixta * pr) / iz);
                        
             double yPiksel0 = yPiksel00 + (yPiksel10 - yPiksel00) * pr - 0.5; // Ako kao sto dole pise da crahuje onda ovde na kraju mozemo dodat * pr - 0.5; Isti efekat. POGLEDAJ DOLE!!!
             double yPiksel1 = yPiksel01 + (yPiksel11 - yPiksel01) * pr;
             
             int yp0 = (int) Math.ceil(yPiksel0); // Plafon je bio nisko a ovo kao resava? // Mada moze da crashuje? Mozda! DA KRESUJE KADA PROMENIM REZ NA NEKU EKSTREMNO NISKU ONDA KRESUJE!!!
             int yp1 = (int) Math.ceil(yPiksel1);
             if (yp0 < 0)
             {
                 yp0 = 0;
             }
             if (yp1 > BVISINA)
             {
                 yp1 = BVISINA;
             }
             
             double ih = 1 / (yPiksel1 - yPiksel0); 
             for (int y = yp0; y < yp1; y++)
             {
                 double pry = (y - yPiksel0) * ih;
                 int yTex = (int) (16 * pry);
                 
                 //pikseli[x + y * BSIRINA] = xTex * 100 + yTex * 100 * 256; // Delete maybe?
                 pikseli[x + y * BSIRINA] = Art.zidovi.pikseli[((xTex) + (tex % 8) * 16) + (yTex + (tex / 8) * 16) * 128] * color; // Sa teksturom. // ZAGRADEEEEEEEEE!!!!!!!!!!!
                 zBuffer[x + y * BSIRINA] = 1 / iz * 4;

             }
        }
    }
    
    public void renderSprite(double x, double y, double z, int tex, int boja) // Rotacija sprajtova mi deluje cudno!?
    {
        //if (this != null)
        //{
        //    return;
        //}
        double xc = (x - xKamera) * 2;
        double yc = (y - zKamera) * 2; // Negde sam pomesao y i z axis...
        double zc = (z - yKamera) * 2; // Negde sam pomesao y i z axis...
        
        double xx = xc * rotCosinus - zc * rotSinus;
        double yy = yc;
        double zz = zc * rotCosinus + xc * rotSinus;
        
        if (zz < 0.1)
        {
            return;
        }
        
        double xPiksel = xCentar - (xx / zz * fov); // Hjaa...!?
        double yPiksel = (yy / zz * fov + yCentar);
        
        double xPiksel0 = xPiksel - BVISINA / zz; 
        double xPiksel1 = xPiksel + BVISINA / zz;
        
        double yPiksel0 = yPiksel - BVISINA / zz; 
        double yPiksel1 = yPiksel + BVISINA / zz;
        
        int xp0 = (int) Math.ceil(xPiksel0);
        int xp1 = (int) Math.ceil(xPiksel1);
        
        int yp0 = (int) Math.ceil(yPiksel0);
        int yp1 = (int) Math.ceil(yPiksel1);
        
        if (xp0 < 0)
        {
            xp0 = 0;
        }
        if (xp1 > BSIRINA)
        {
            xp1 = BSIRINA;
        }
        
        if (yp0 < 0)
        {
            yp0 = 0;
        }
        if (yp1 > BVISINA)
        {
            yp1 = BVISINA;
        }
        
        zz *= 4;
        
        for (int yp = yp0; yp < yp1; yp++)
        {
            double ypr = (yp - yPiksel0) / (yPiksel1 - yPiksel0);
            int yt = (int) (ypr * 16); 
            for (int xp = xp0; xp < xp1; xp++)
            {
                double xpr = (xp - xPiksel0) / (xPiksel1 - xPiksel0);
                int xt = (int) (xpr * 16);
                if (zBuffer[xp + yp * BSIRINA] > zz)
                {
                    int temp = Art.sprajtovi.pikseli[(xt + tex % 8 * 16) + (yt + (tex / 8) * 16) * 128];
                    if (temp >= 0)
                    {
                        pikseli[xp + yp * BSIRINA] = temp * boja;
                        //pikseli[xp + yp * BSIRINA] = xt * 32 + yt * 32 * 256;
                        zBuffer[xp + yp * BSIRINA] = zz;
                    }
                }
            }
        }
    }
   
    public void postProcess() // Ne nije fxaa, lol
    {
        //if (this != null)
        //{
        //    return;
        //}
        //Random random = new Random(10);
        for (int i = 0; i < BSIRINA * BVISINA; i++)
        {
            //int xp = (i % BSIRINA);
            //int yp = (i / BSIRINA) * 14; 
            
            double xx = ((i % BSIRINA - BSIRINA / 2.0) / BSIRINA);
            int boja = pikseli[i];
            //int osvetljenje = (int) (15900 / (zBuffer[i] * zBuffer[i])); // Za sada imam ovaj pp da koristim. Mada i ovaj ima problem.
            //int osvetljenje = (int) (255 - zBuffer[i] * 5); // Ne svidja mi se kako izgleda.
            int osvetljenje = (int) (255 - zBuffer[i] * 5 * (xx * xx * 2 + 1)); // Ovo wje najbolje?
            //int osvetljenje = (int) (300 - zBuffer[i] * 6 * (xx * xx * 2 + 1));
            //int osvetljenje = (int) (6000 / (zBuffer[i] * zBuffer[i]) * 6 / (xx * xx * 2 + 1));
            //intosvetljenje = osvetljenje >> 6 << 6; // Striping je dosadan i bezveze ali svidja mi se izgled.
            //osvetljenje = (osvetljenje + ((xp + yp) & 16) * 8) >> 6 << 6; //Hmmmm ne bas
            if (osvetljenje < 0)
            {
                osvetljenje = 0;
            }
            if (osvetljenje > 255)
            {
                osvetljenje = 255;
            }
           
            int crvena = (boja >> 16) & 0xFF;
            int zelena = (boja >> 8) & 0xFF;
            int plava = (boja) & 0xFF;
           
            crvena = crvena * osvetljenje / 255;
            zelena = zelena * osvetljenje / 255;
            plava = plava * osvetljenje / 255;
           
            pikseli[i] = crvena << 16 | zelena << 8 | plava;
        }
    }
}
